//! Player tracking state.
#![allow(dead_code)]

use craftdig_math::{Vec3f, AABB};
use craftdig_sim::phys::{PhysicsEntity, PhysicsUpdate};

/// State that tracks the player's world state in advance of the sim tick.
///
/// We always have our own view of this and push updates to the sim tick since
/// we want our physics to feel snappier than the game can handle.
#[derive(Copy, Clone, Debug)]
pub struct PlayerState {
    /// Local copy of position.
    pos: Vec3f,

    /// Velocity.
    vel: Vec3f,

    /// If we're grounded or not.
    grounded: bool,
}

impl PlayerState {
    pub fn new(pos: Vec3f, vel: Vec3f) -> Self {
        Self {
            pos,
            vel,
            grounded: false,
        }
    }

    pub fn new_at(pos: Vec3f) -> Self {
        Self::new(pos, Vec3f::zero())
    }

    pub fn pos(&self) -> Vec3f {
        self.pos
    }

    pub fn set_pos(&mut self, pos: Vec3f) {
        self.pos = pos;
    }

    pub fn vel(&self) -> Vec3f {
        self.vel
    }

    pub fn set_vel(&mut self, vel: Vec3f) {
        self.vel = vel;
    }

    pub fn grounded(&self) -> bool {
        self.grounded
    }

    pub fn set_grounded(&mut self, grounded: bool) {
        self.grounded = grounded;
    }

    pub fn apply_physics_update(&mut self, update: &PhysicsUpdate) {
        if let Some(npos) = update.new_pos() {
            self.pos = npos;
        }

        if let Some(nvel) = update.new_vel() {
            self.vel = nvel;
        }

        if let Some(g) = update.new_grounded() {
            self.grounded = g;
        }
    }
}

pub struct PlayerPhysics {
    aabb: AABB,
    pstate: PlayerState,
    obeys_gravity: bool,
}

impl PlayerPhysics {
    pub fn new(aabb: AABB, pstate: PlayerState, obeys_gravity: bool) -> Self {
        Self {
            aabb,
            pstate,
            obeys_gravity,
        }
    }
}

impl PhysicsEntity for PlayerPhysics {
    fn collider(&self) -> &AABB {
        &self.aabb
    }

    fn position(&self) -> Vec3f {
        self.pstate.pos()
    }

    fn velocity(&self) -> Vec3f {
        self.pstate.vel()
    }

    fn obeys_gravity(&self) -> bool {
        self.obeys_gravity
    }

    fn is_grounded(&self) -> bool {
        self.pstate.grounded
    }
}

/// Player control inputs extracted from the engine.  This should be a directish
/// representation of the user's request without any processing to account for
/// if actions should be possible at some given moment.
#[derive(Copy, Clone, Debug, Default)]
pub struct ControlInput {
    /// The player's requested movement direction.
    ///
    /// This should be a unit vector, already accounting for the diagonal thing.
    movement: Vec3f,

    /// If the player initiated a jump.
    jumped: bool,

    /// If the player is trying to crouch right now.
    // TODO implement this
    crouched: bool,

    /// If the player is trying to sprint right now.
    // TODO implement this
    sprint: bool,
}

impl ControlInput {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn movement(&self) -> Vec3f {
        self.movement
    }

    pub fn set_movement(&mut self, movement: Vec3f) {
        self.movement = movement;
    }

    pub fn jumped(&self) -> bool {
        self.jumped
    }

    pub fn set_jumped(&mut self, jumped: bool) {
        self.jumped = jumped;
    }

    // TODO other accessors when we implemented them
}
